package com.aaron.gesturehelper.gesturelib;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;

public class MyGestureStroke {
	static final float TOUCH_TOLERANCE = 3;

	public final RectF boundingBox;

	public final float length;
	public final float[] points;

	private final long[] timestamps;
	private Path mCachedPath;

	/**
	 * A constructor that constructs a gesture stroke from a list of gesture
	 * points.
	 * 
	 * @param points
	 */
	public MyGestureStroke(ArrayList<MyGesturePoint> points) {
		final int count = points.size();
		final float[] tmpPoints = new float[count * 2];
		final long[] times = new long[count];

		RectF bx = null;
		float len = 0;
		int index = 0;

		for (int i = 0; i < count; i++) {
			final MyGesturePoint p = points.get(i);
			tmpPoints[i * 2] = p.x;
			tmpPoints[i * 2 + 1] = p.y;
			times[index] = p.timestamp;

			if (bx == null) {
				bx = new RectF();
				bx.top = p.y;
				bx.left = p.x;
				bx.right = p.x;
				bx.bottom = p.y;
				len = 0;
			} else {
				len += Math.sqrt(Math.pow(p.x - tmpPoints[(i - 1) * 2], 2)
						+ Math.pow(p.y - tmpPoints[(i - 1) * 2 + 1], 2));
				bx.union(p.x, p.y);
			}
			index++;
		}

		timestamps = times;
		this.points = tmpPoints;
		boundingBox = bx;
		length = len;
	}

	/**
	 * A faster constructor specially for cloning a stroke.
	 */
	private MyGestureStroke(RectF bbx, float len, float[] pts, long[] times) {
		boundingBox = new RectF(bbx.left, bbx.top, bbx.right, bbx.bottom);
		length = len;
		points = pts.clone();
		timestamps = times.clone();
	}

	@Override
	public Object clone() {
		return new MyGestureStroke(boundingBox, length, points, timestamps);
	}

	/**
	 * Draws the stroke with a given canvas and paint.
	 * 
	 * @param canvas
	 */
	void draw(Canvas canvas, Paint paint) {
		if (mCachedPath == null) {
			makePath();
		}

		canvas.drawPath(mCachedPath, paint);
	}

	public Path getPath() {
		if (mCachedPath == null) {
			makePath();
		}

		return mCachedPath;
	}

	private void makePath() {
		final float[] localPoints = points;
		final int count = localPoints.length;

		Path path = null;

		float mX = 0;
		float mY = 0;

		for (int i = 0; i < count; i += 2) {
			float x = localPoints[i];
			float y = localPoints[i + 1];
			if (path == null) {
				path = new Path();
				path.moveTo(x, y);
				mX = x;
				mY = y;
			} else {
				float dx = Math.abs(x - mX);
				float dy = Math.abs(y - mY);
				if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
					path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
					mX = x;
					mY = y;
				}
			}
		}

		mCachedPath = path;
	}

	/**
	 * Converts the stroke to a Path of a given number of points.
	 * 
	 * @param width
	 *            the width of the bounding box of the target path
	 * @param height
	 *            the height of the bounding box of the target path
	 * @param numSample
	 *            the number of points needed
	 * 
	 * @return the path
	 */
	public Path toPath(float width, float height, int numSample) {
		final float[] pts = MyGestureUtil.temporalSampling(this, numSample);
		final RectF rect = boundingBox;

		MyGestureUtil.translate(pts, -rect.left, -rect.top);

		float sx = width / rect.width();
		float sy = height / rect.height();
		float scale = sx > sy ? sy : sx;
		MyGestureUtil.scale(pts, scale, scale);

		float mX = 0;
		float mY = 0;

		Path path = null;

		final int count = pts.length;

		for (int i = 0; i < count; i += 2) {
			float x = pts[i];
			float y = pts[i + 1];
			if (path == null) {
				path = new Path();
				path.moveTo(x, y);
				mX = x;
				mY = y;
			} else {
				float dx = Math.abs(x - mX);
				float dy = Math.abs(y - mY);
				if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
					path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
					mX = x;
					mY = y;
				}
			}
		}

		return path;
	}

	void serialize(DataOutputStream out) throws IOException {
		final float[] pts = points;
		final long[] times = timestamps;
		final int count = points.length;

		// Write number of points
		out.writeInt(count / 2);

		for (int i = 0; i < count; i += 2) {
			// Write X
			out.writeFloat(pts[i]);
			// Write Y
			out.writeFloat(pts[i + 1]);
			// Write timestamp
			out.writeLong(times[i / 2]);
		}
	}

	static MyGestureStroke deserialize(DataInputStream in) throws IOException {
		// Number of points
		final int count = in.readInt();

		final ArrayList<MyGesturePoint> points = new ArrayList<MyGesturePoint>(
				count);
		for (int i = 0; i < count; i++) {
			points.add(MyGesturePoint.deserialize(in));
		}

		return new MyGestureStroke(points);
	}

	/**
	 * Invalidates the cached path that is used to render the stroke.
	 */
	public void clearPath() {
		if (mCachedPath != null)
			mCachedPath.rewind();
	}
}
